// ============================================================================
//
// Copyright (C) 2006-2007 Dengues
//
// Google Group: http://groups.google.com/group/dengues
// QQ Group: 24885404
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// ============================================================================
package org.dengues.commons.jdk.xpath;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.dengues.commons.i18n.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Qiang.Zhang.Adolf@gmail.com class global comment. Detailled comment <br/>
 * 
 * $Id: Dengues.epf 1 2006-09-29 17:06:40Z qiang.zhang $
 * 
 * @see http://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html.
 */
public class ComplexXPathUsedXalan {

    private final File xmlInput;

    private Document document;

    private final XPath xPath;

    public static final String DEFAULT_PRE = "pre" + Math.random(); //$NON-NLS-1$

    private final Map<String, String> prefixToNamespace = new HashMap<String, String>();

    /**
     * Qiang.Zhang.Adolf@gmail.com ComplexXPathUsedXalan constructor comment.
     * 
     * @param xmlFilename
     */
    public ComplexXPathUsedXalan(String xmlFilename) {
        xmlInput = new File(xmlFilename);
        if (!xmlInput.exists()) {
            throw new RuntimeException(Messages.getString("ComplexXPathUsedXalan.empty")); //$NON-NLS-1$
        }
        try {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            docFactory.setNamespaceAware(true);
            DocumentBuilder builder = docFactory.newDocumentBuilder();
            document = builder.parse(xmlInput);
            initLastNodes(document.getDocumentElement());
            prefixToNamespace.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException exception) {
            exception.printStackTrace();
        }
        XPathFactory factory = XPathFactory.newInstance();
        xPath = factory.newXPath();
        xPath.setNamespaceContext(getNamespaceContext());
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "initLastNodes".
     * 
     * @param node
     */
    private void initLastNodes(Node node) {
        NodeList childNodes = node.getChildNodes();
        int length = childNodes.getLength();
        int type = node.getNodeType();
        if (type == Node.ELEMENT_NODE) {
            setPrefixToNamespace(node);
        }
        for (int i = 0; i < length; i++) {
            Node item = childNodes.item(i);
            if (item.getChildNodes().getLength() > 0) {
                initLastNodes(item);
            }
        }
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "setPrefixToNamespace".
     * 
     * @param node
     */
    private void setPrefixToNamespace(Node node) {
        NamedNodeMap nnm = node.getAttributes();
        for (int i = 0; i < nnm.getLength(); i++) {
            Node attr = nnm.item(i);
            String aname = attr.getNodeName();
            boolean isPrefix = aname.startsWith(XMLConstants.XMLNS_ATTRIBUTE + ":"); //$NON-NLS-1$
            if (isPrefix || aname.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
                int index = aname.indexOf(':');
                String p = isPrefix ? aname.substring(index + 1) : XMLConstants.NULL_NS_URI;
                prefixToNamespace.put(p, attr.getNodeValue());
            }
        }
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "parseXPath".
     * 
     * @param expression
     * @param name
     * @return
     */
    public Object parseXPath(String expression, QName name) {
        try {
            expression = addDefaultPrefix(expression);
            XPathExpression xexpr = xPath.compile(expression);
            Object cd = xexpr.evaluate(getDocumnent(), name);
            return cd;
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "addDefaultPrefix".
     * 
     * @param xPathExpression
     * @return
     */
    private String addDefaultPrefix(String xPathExpression) {
        if (XMLConstants.NULL_NS_URI.equals(getDefaultNamespace())) {
            return xPathExpression;
        } else {
            StringBuilder expr = new StringBuilder();
            String[] split = xPathExpression.split("/"); //$NON-NLS-1$
            for (String string : split) {
                if (!string.equals("") && string.indexOf(':') == -1 && string.indexOf('.') == -1) { //$NON-NLS-1$
                    expr.append(DEFAULT_PRE + ":"); //$NON-NLS-1$
                }
                expr.append(string + "/"); //$NON-NLS-1$
            }
            if (split.length > 0) {
                expr.deleteCharAt(expr.length() - 1);
            }
            return expr.toString();
        }
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getNamespaceContext".
     * 
     * @return
     */
    private NamespaceContext getNamespaceContext() {
        return new NamespaceContext() {

            public String getNamespaceURI(String prefix) {
                String namespaceForPrefix = getNamespaceForPrefix(prefix);
                return namespaceForPrefix;

            }

            public java.util.Iterator getPrefixes(String val) {
                return null;
            }

            public String getPrefix(String uri) {
                return null;
            }
        };
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getNamespaceForPrefix".
     * 
     * @param prefix
     * @return
     */
    protected String getNamespaceForPrefix(String prefix) {
        String namespace = prefixToNamespace.get(prefix);
        if (namespace != null) {
            return namespace;
        }
        return getDefaultNamespace();
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getDefaultNamespace".
     * 
     * @return
     */
    private String getDefaultNamespace() {
        Node parent = document.getDocumentElement();
        int type = parent.getNodeType();
        if (type == Node.ELEMENT_NODE) {
            NamedNodeMap nnm = parent.getAttributes();
            for (int i = 0; i < nnm.getLength(); i++) {
                Node attr = nnm.item(i);
                String aname = attr.getNodeName();
                if (aname.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
                    return attr.getNodeValue();
                }
            }
        }
        return XMLConstants.NULL_NS_URI;
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getDocumnent".
     * 
     * @return
     */
    protected Document getDocumnent() {
        return document;
    }

}
